package ar.com.e2solver.solver.operator.localsearch.mt;

import java.util.ArrayList;
import java.util.List;

import ar.com.e2solver.exception.BusinessException;
import ar.com.e2solver.model.Coordinate;
import ar.com.e2solver.model.NeighbourDirectionEnum;
import ar.com.e2solver.model.Piece;
import ar.com.e2solver.model.Puzzle;
import ar.com.e2solver.solver.Individual;
import ar.com.e2solver.solver.RandomConstants;
import ar.com.e2solver.solver.operator.OperatorUtil;

public class WeightedSwapClimbWorker extends ClimbWorker {

	public static final double WEIGHTFACTOR_VARIABLE = 0.915 * 0.64;
	public static final double WEIGHTFACTOR_FIXED = 0.915 * 2.6;
	public static final int ITERATIONS = 2000;

	public WeightedSwapClimbWorker(Individual individual, List<Individual> resultList, int internalNumber) {
		super(individual, resultList, internalNumber);
	}

	@Override
	protected List<Individual> climb(Individual individual) throws BusinessException {
		Puzzle puzzle = individual.getPuzzle().clone();
		Individual best = new Individual(puzzle);
		Individual current = best;
		long[][] weights;
		
		for(int i = 0; i < ITERATIONS; i++){
			weights = computeWeights(puzzle);
			Coordinate coordA = computeSwapCoord(puzzle, weights);
			adaptWeights(puzzle, weights, coordA);
			
			if(getTotalWeight(weights, puzzle) != 0){
				Coordinate coordB = computeSwapCoord(puzzle, weights);

				puzzle.swapPositions(coordA, coordB);
				
				if(puzzle.getPieceAt(coordA).isABorderPiece()){
					OperatorUtil.rotateForBorder(coordA, puzzle);
					OperatorUtil.rotateForBorder(coordB, puzzle);
				} else if(puzzle.getPieceAt(coordA).isACornerPiece()){
					OperatorUtil.rotateForCorner(coordA, puzzle);
					OperatorUtil.rotateForCorner(coordB, puzzle);
				} else {
					OperatorUtil.rotate(coordA, puzzle);
					OperatorUtil.rotate(coordB, puzzle);
				}
				
				current = new Individual(puzzle);
				if(current.getFitnessValue() > best.getFitnessValue()){
					best = new Individual(current.getPuzzle().clone());
				}
			} else {
				System.out.println("no tiene alternativa");
			}
		}
		
		List<Individual> result = new ArrayList<Individual>(2);
		result.add(best);
		result.add(current);
		return result;
	}
	
	private long[][] computeWeights(Puzzle puzzle) {
		double weightFactor = WEIGHTFACTOR_FIXED + WEIGHTFACTOR_VARIABLE * puzzle.getColumnNumbers();
		long[][] weights = new long[puzzle.getRowsNumber()][puzzle.getColumnNumbers()];
		
		Coordinate coordinate;
		long weight = 0;
		for (int i = 0; i < puzzle.getRowsNumber(); i++) {
			for (int j = 0; j < puzzle.getColumnNumbers(); j++) {
				weight = 0;
				coordinate = new Coordinate(i, j);
				if(!puzzle.getFixedCoordinates().contains(coordinate)){
					weight = evaluateWeight(puzzle, coordinate) + 1;
					weight = (long) Math.pow(weight, weightFactor);
				}
				
				weights[i][j] = weight;
			}
		}
					
		return weights;
	}

	private long evaluateWeight(Puzzle puzzle, Coordinate coordinate) {
		Piece thePiece = puzzle.getPieceAt(coordinate);
		Piece left = puzzle.getNeighbour(coordinate, NeighbourDirectionEnum.LEFT);
		Piece top = puzzle.getNeighbour(coordinate, NeighbourDirectionEnum.TOP);
		Piece right = puzzle.getNeighbour(coordinate, NeighbourDirectionEnum.RIGHT);
		Piece bottom = puzzle.getNeighbour(coordinate, NeighbourDirectionEnum.BOTTOM);
		long weight = 0;
		
		if(left != null){
			if(!left.getRight().equals(thePiece.getLeft())){
				weight++;
			}
		}
		
		if(top!= null){
			if(!top.getBottom().equals(thePiece.getTop())){
				weight++;
			}
		}
		
		if(right != null){
			if(!right.getLeft().equals(thePiece.getRight())){
				weight++;
			}
		}
		
		if(bottom != null){
			if(!bottom.getTop().equals(thePiece.getBottom())){
				weight++;
			}
		}
		
		Piece p = puzzle.getPieceAt(coordinate);
		if((p.isABorderPiece() || p.isACornerPiece()) && weight > 1){
			weight++;
		}
		
		
		return weight;
	}
	
	private Coordinate computeSwapCoord(Puzzle puzzle, long[][] weights) {
		long totalWeight = getTotalWeight(weights, puzzle);
		long abspos = Math.abs(RandomConstants.RANDOM.nextLong()) % totalWeight;
		
		Coordinate coordinate = null;
		for (int i = 0; i < puzzle.getRowsNumber() && abspos >= 0; i++) {
			for (int j = 0; j < puzzle.getColumnNumbers() && abspos >= 0; j++) {
				abspos -= weights[i][j];
				coordinate = new Coordinate(i, j);
			}
		}

		return coordinate;
	}

	private long getTotalWeight(long[][] weights, Puzzle puzzle) {
		long totalWeight = 0;
		for (int i = 0; i < puzzle.getRowsNumber(); i++) {
			for (int j = 0; j < puzzle.getColumnNumbers(); j++) {
				totalWeight = totalWeight + weights[i][j];
			}
		}
		
		return totalWeight;
	}
	
	private void adaptWeights(Puzzle puzzle, long[][] weights, Coordinate c) {
		weights[c.getX()][c.getY()] = 0;
		
		Piece piece = puzzle.getPieceAt(c);
		if(piece.isABorderPiece()){
			takeOutCorners(weights, puzzle);
			takeOutInternal(weights, puzzle);
		} else if(piece.isACornerPiece()){
			takeOutInternal(weights, puzzle);
			takeOutBorders(weights, puzzle);
		} else {
			takeOutBorders(weights, puzzle);
			takeOutCorners(weights, puzzle);
		}
	}

	private void takeOutInternal(long[][] weights, Puzzle puzzle) {
		for (int i = 1; i < puzzle.getRowsNumber() - 1; i++) {
			for (int j = 1; j < puzzle.getColumnNumbers() - 1; j++) {
				weights[i][j] = 0;				
			}
		}
	}
	
	private void takeOutBorders(long[][] weights, Puzzle puzzle) {
		for (int i = 1; i < puzzle.getRowsNumber() - 1; i++) {
			weights[i][0] = 0;
			weights[i][puzzle.getColumnNumbers() -1] = 0;
		}
		
		for (int j = 1; j < puzzle.getColumnNumbers() - 1; j++) {
			weights[0][j] = 0;
			weights[puzzle.getRowsNumber() - 1][j] = 0;				
		}
	}

	private void takeOutCorners(long[][] weights, Puzzle puzzle) {
		weights[0][0] = 0;
		weights[0][puzzle.getColumnNumbers() -1] = 0;
		weights[puzzle.getRowsNumber() -1][0] = 0;
		weights[puzzle.getRowsNumber() -1][puzzle.getColumnNumbers() -1] = 0;
	}
}
